package com.jsbs.iam.common.core.utils;

import com.jsbs.iam.common.core.dto.CodeAndCont;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.*;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static org.apache.http.HttpStatus.SC_OK;

public class HttpUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(HttpUtil.class);

    private static CloseableHttpClient httpClient;
    private static RequestConfig commonRequestConfig;

    static {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(2000);
        cm.setDefaultMaxPerRoute(1000);
        httpClient = HttpClients.custom().setConnectionManager(cm).setRetryHandler(new CommonRetryHandler()).build();

        commonRequestConfig = RequestConfig.custom()
                .setSocketTimeout(5000)
                .setConnectTimeout(2000)
                .build();
    }

    public static String post(String url, Map<String, String> paramPairs, int connectionTimeout, int socketTimeout) {
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(socketTimeout)
                .setConnectTimeout(connectionTimeout)
                .build();
        return post(url, paramPairs, requestConfig);
    }

    public static String post(String url, Map<String, String> paramPairs) {
        return post(url, paramPairs, commonRequestConfig);
    }

    private static String post(String url, Map<String, String> paramPairs, RequestConfig requestConfig) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(requestConfig);
        ArrayList nameValuePairs = new ArrayList(paramPairs.size());

        for (String key : paramPairs.keySet()) {
            nameValuePairs.add(new BasicNameValuePair(key, paramPairs.get(key)));
        }

        CloseableHttpResponse response = null;
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, Consts.UTF_8));
            LOGGER.info("URL:{} 发起请求，请求参数:{}", httpPost.getURI(), Json.toJsonString(paramPairs));
            response = httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            String result = EntityUtils.toString(response.getEntity());
            if (statusCode == SC_OK) {
                LOGGER.info("URL:{} 结束请求，响应码:{}，响应结果:{}", httpPost.getURI(), statusCode, result);
                return result;
            } else {
                LOGGER.error("URL:{} 结束请求，响应码:{}，响应结果:{}", httpPost.getURI(), statusCode, result);
                return null;
            }
        } catch (Exception e) {
            LOGGER.error(e.toString(), e);
            return null;
        } finally {
            closeRsp(response);
        }
    }

    public static String get(String url, Map<String, String> paramPairs, int connectionTimeout, int socketTimeout, Map<String, String> headerPairs) {
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(socketTimeout)
                .setConnectTimeout(connectionTimeout)
                .build();
        return get(url, paramPairs, requestConfig, headerPairs);
    }


    public static String get(String url, Map<String, String> paramPairs, Map<String, String> headerPairs) {
        return get(url, paramPairs, commonRequestConfig, headerPairs);
    }

    private static String get(String url, Map<String, String> paramPairs, RequestConfig requestConfig, Map<String, String> headerPairs) {
        ArrayList nameValuePairs = new ArrayList(paramPairs.size());

        for (String key : paramPairs.keySet()) {
            nameValuePairs.add(new BasicNameValuePair(key, paramPairs.get(key)));
        }

        CloseableHttpResponse response = null;
        try {
            URI uri = new URIBuilder(url).addParameters(nameValuePairs).build();
            HttpGet httpGet = new HttpGet(uri);
            httpGet.setConfig(requestConfig);
            if (Objects.nonNull(headerPairs)) {
                for (Map.Entry<String, String> entry : headerPairs.entrySet()) {
                    httpGet.setHeader(entry.getKey(), entry.getValue());
                }
            }
            LOGGER.info("URL:{} 发起请求", httpGet.getURI().toString());
            response = httpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            String result = EntityUtils.toString(response.getEntity());
            if (statusCode == SC_OK) {
                LOGGER.info("URL:{} 结束请求，响应码:{}，响应结果:{}", httpGet.getURI(), statusCode, result);
                return result;
            } else {
                LOGGER.error("URL:{} 结束请求，响应码:{}，响应结果:{}", httpGet.getURI(), statusCode, result);
                return null;
            }
        } catch (Exception e) {
            LOGGER.error(e.toString(), e);
            return null;
        } finally {
            closeRsp(response);
        }
    }

    public static String postJson(String url, String json, int connectionTimeout, int socketTimeout) {
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(socketTimeout)
                .setConnectTimeout(connectionTimeout)
                .build();
        return postJson(url, json, requestConfig);
    }

    public static String postJson(String url, String json) {
        return postJson(url, json, commonRequestConfig);
    }

    public static CodeAndCont rawPostJson(String url, String json) throws IOException {
        return rawPostJson(url, json, commonRequestConfig);
    }

    public static CodeAndCont rawPostJson(String url, String json, int connectionTimeout, int socketTimeout) throws IOException {
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(socketTimeout)
                .setConnectTimeout(connectionTimeout)
                .build();
        return rawPostJson(url, json, requestConfig);
    }

    private static CodeAndCont rawPostJson(String url, String json, RequestConfig requestConfig, Header... headers) throws IOException {
        CloseableHttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
            if (headers != null && headers.length > 0) {
                Arrays.stream(headers).forEach(item -> httpPost.addHeader(item));
            }
            StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPost.setEntity(entity);
            LOGGER.info("URL:{} 发起请求，请求参数:{}", httpPost.getURI(), json);
            try {
                response = httpClient.execute(httpPost);
            } catch (IOException e) {
                LOGGER.error("URL:{} 请求失败，原因:{}", httpPost.getURI(), e.toString());
                throw e;
            }
            int statusCode = response.getStatusLine().getStatusCode();
            String result = EntityUtils.toString(response.getEntity());
            if (statusCode == SC_OK) {
                LOGGER.info("URL:{} 结束请求，响应码:{}，响应结果:{}", httpPost.getURI(), statusCode, result);
                return new CodeAndCont(statusCode, result);
            } else {
                LOGGER.error("URL:{} 结束请求，响应码:{}，响应结果:{}", httpPost.getURI(), statusCode, result);
                return new CodeAndCont(statusCode, result);
            }
        } finally {
            closeRsp(response);
        }
    }


    public static String postJsonWithBasic(String url, String json, String userName, String password) {
        //https://cloud.tencent.com/developer/article/1499759
        String auth = userName + ":" + password;
        byte[] encodedAuth = Base64.encodeBase64(
                auth.getBytes(StandardCharsets.UTF_8));
        String authHeader = "Basic " + new String(encodedAuth);
        // 将验证信息放入到 Header
        Header basicHeader = new BasicHeader(HttpHeaders.AUTHORIZATION, authHeader);
        return postJson(url, json, commonRequestConfig, basicHeader);
    }

    private static String postJson(String url, String json, RequestConfig requestConfig, Header... headers) {
        CloseableHttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
            if (headers != null && headers.length > 0) {
                Arrays.stream(headers).forEach(httpPost::addHeader);
            }
            StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPost.setEntity(entity);
            LOGGER.info("URL:{} 发起请求，请求参数:{}", httpPost.getURI(), json);
            response = httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            String result = EntityUtils.toString(response.getEntity());
            if (statusCode == SC_OK) {
                LOGGER.info("URL:{} 结束请求，响应码:{}，响应结果:{}", httpPost.getURI(), statusCode, result);
                return result;
            } else {
                LOGGER.error("URL:{} 结束请求，响应码:{}，响应结果:{}", httpPost.getURI(), statusCode, result);
                return null;
            }
        } catch (Exception e) {
            LOGGER.error(e.toString(), e);
            return null;
        } finally {
            closeRsp(response);
        }
    }

    public static String postUrl(String url) {
        CloseableHttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            LOGGER.info("URL:{} 发起请求", httpPost.getURI());
            response = httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            String result = EntityUtils.toString(response.getEntity());
            if (statusCode == SC_OK) {
                LOGGER.info("URL:{} 结束请求，响应码:{}，响应结果:{}", httpPost.getURI(), statusCode, result);
                return result;
            } else {
                LOGGER.error("URL:{} 结束请求，响应码:{}，响应结果:{}", httpPost.getURI(), statusCode, result);
                return null;
            }
        } catch (Exception e) {
            LOGGER.error(e.toString(), e);
            return null;
        } finally {
            closeRsp(response);
        }
    }


    private static void closeRsp(CloseableHttpResponse response) {
        if (response != null) {
            try {
                response.close();
            } catch (IOException e) {
                //do nothing
            }
        }
    }

    private static class CommonRetryHandler extends DefaultHttpRequestRetryHandler {
        private final int retryCount = 3;
        private final List<Class<? extends IOException>> nonRetriableClasses;

        public CommonRetryHandler() {
            nonRetriableClasses = Arrays.asList(
                    InterruptedIOException.class,
                    UnknownHostException.class,
                    ConnectException.class,
                    SSLException.class);
        }

        @Override
        public boolean retryRequest(
                final IOException exception,
                final int executionCount,
                final HttpContext context) {
            Args.notNull(exception, "Exception parameter");
            Args.notNull(context, "HTTP context");
            if (executionCount > retryCount) {
                // Do not retry if over max retry count
                return false;
            }

            if (exception instanceof NoHttpResponseException) {
                return true;
            }

            for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) {
                if (rejectException.isInstance(exception)) {
                    return false;
                }
            }
            final HttpClientContext clientContext = HttpClientContext.adapt(context);
            final HttpRequest request = clientContext.getRequest();


            if (handleAsIdempotent(request)) {
                // Retry if the request is considered idempotent
                return true;
            }

            if (!clientContext.isRequestSent()) {
                // Retry if the request has not been sent fully
                return true;
            }
            // otherwise do not retry
            return false;
        }
    }
}